package cn.zhenglili.uflodemo.service.impl;


import cn.zhenglili.uflodemo.entity.TaskInfo;
import cn.zhenglili.uflodemo.mapper.TaskInfoMapper;
import cn.zhenglili.uflodemo.service.BusinessService;
import cn.zhenglili.uflodemo.service.feign.FormDesignerService;
import cn.zhenglili.uflodemo.service.feign.OnlineService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bstek.uflo.console.handler.impl.list.AssigneeInfo;
import com.bstek.uflo.console.provider.ProcessFile;
import com.bstek.uflo.console.provider.ProcessProvider;
import com.bstek.uflo.console.provider.ProcessProviderUtils;
import com.bstek.uflo.model.HistoryTask;
import com.bstek.uflo.model.ProcessDefinition;
import com.bstek.uflo.model.ProcessInstance;
import com.bstek.uflo.model.task.Task;
import com.bstek.uflo.model.task.TaskState;
import com.bstek.uflo.process.assign.Assignee;
import com.bstek.uflo.process.assign.AssigneeProvider;
import com.bstek.uflo.process.assign.Entity;
import com.bstek.uflo.process.assign.PageQuery;
import com.bstek.uflo.process.node.Node;
import com.bstek.uflo.query.ProcessQuery;
import com.bstek.uflo.service.HistoryService;
import com.bstek.uflo.service.ProcessService;
import com.bstek.uflo.service.StartProcessInfo;
import com.bstek.uflo.service.TaskService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.annotation.Resource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;

/**
 * @ClassName：BusinessService
 * @Description：TODO
 * @Author：zhenglili
 * @Date：2020/9/4 15:43
 **/
@Service
public class BusinessServiceImpl implements BusinessService {

    @Value("${uflo.defaultFileStoreDir}")
    private String xmlSavePath;

    /**
     * uflo自带service,用于流程部署
     */
    @Autowired
    private ProcessService processService;

    /**
     * 表单设计器微服务接口
     */
    @Autowired
    private FormDesignerService formDesignerService;

    /**
     * online表单微服务接口
     */
    @Autowired
    private OnlineService onlineService;


    @Resource
    private TaskInfoMapper taskInfoMapper;

    @Resource
    private TaskService taskService;

    @Resource
    private HistoryService historyService;


    /**
     * 获取可选的参与者类型集合
     *
     * @param applicationContext
     * @return
     */
    @Override
    public Map<String, AssigneeProvider> assigneeProviderMap(ApplicationContext applicationContext) {
        Map<String, AssigneeProvider> assigneeProviderMap = new HashMap<>();
        // 获取AssigneeProvider类型的bean
        // 获取可选的参与者类型,例如deptAssigneeProvider和userAssigneeProvider
        Map<String, AssigneeProvider> map = applicationContext.getBeansOfType(AssigneeProvider.class);
        for (String key : map.keySet()) {
            AssigneeProvider assigneeProvider = map.get(key);
            if (!assigneeProvider.disable()) {
                assigneeProviderMap.put(key, assigneeProvider);
            }
        }
        return assigneeProviderMap;
    }

    /**
     * 根据providerKey设置对应人员类型的基本信息
     *
     * @param assigneeProviderMap
     * @param providerKey
     * @return
     */
    @Override
    public AssigneeInfo bulidAssigneeInfo(Map<String, AssigneeProvider> assigneeProviderMap, String providerKey) {
        // providerKey一定要是一个准确的值
        AssigneeProvider assigneeProvider = assigneeProviderMap.get(providerKey);
        // 设置该类型基本信息
        AssigneeInfo info = new AssigneeInfo();
        info.setProviderId(providerKey);
        info.setName(assigneeProvider.getName());
        info.setTree(assigneeProvider.isTree());
        return info;
    }

    /**
     * 有详细的类型providerKey，获取对应的人员
     *
     * @param assigneeProviderMap
     * @param providerKey
     * @param pageQuery
     * @param keyWord
     * @return
     */
    @Override
    public AssigneeInfo buildAssigneeInfoDetails(Map<String, AssigneeProvider> assigneeProviderMap, String providerKey,
                                                 PageQuery<Entity> pageQuery, String keyWord) {
        // 查询类型信息
        AssigneeInfo assigneeInfo = bulidAssigneeInfo(assigneeProviderMap, providerKey);
        // 查询内部人员信息
        AssigneeProvider assigneeProvider = assigneeProviderMap.get(providerKey);
        assigneeProvider.queryEntities(pageQuery, keyWord);
        List<Assignee> assignees = new ArrayList<>();
        Collection<Entity> entitys = pageQuery.getResult();
        for (Entity entity : entitys) {
            Assignee assignee = new Assignee();
            assignee.setId(entity.getId());
            assignee.setName(entity.getName());
            assignee.setProviderId(assigneeInfo.getProviderId());
            assignees.add(assignee);
        }
        // 此时的总数是没分页时的总个数
        assigneeInfo.setCount(pageQuery.getRecordCount());
        assigneeInfo.setAssignees(assignees);
        return assigneeInfo;
    }


    /**
     * 查询文件名是否存在，存在为true，不存在为false
     *
     * @param fileName
     * @return
     */
    @Override
    public boolean fileNameIsExist(String fileName) {
        // ProcessProviderUtils.getProcessProviderByName(providerName)根据分类名称获取对应processProvider
        ProcessProvider processProvider = ProcessProviderUtils.getProcessProviderByName("默认文件系统");
        List<ProcessFile> processFiles = processProvider.loadAllProcesses();
        for (ProcessFile processFile : processFiles) {
            if (fileName.equals(processFile.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 删除流程定义文件
     *
     * @param fileName
     */
    @Override
    public void deleteFile(String fileName) {
        // ProcessProviderUtils.getProcessProvider(fileName)根据文件名称获取对应ProcessProvider,fileName格式file:xxx.uflo.xml
        ProcessProvider processProvider = ProcessProviderUtils.getProcessProvider(fileName);
        processProvider.deleteProcess(fileName);
    }


    /**
     * 根据fileName部署流程
     * 受影响表： 1 uflo_blob 2 uflo_process
     *
     * @param fileName
     */
    @Override
    public void deployProcessByFileName(String fileName) {
        ProcessProvider processProvider = ProcessProviderUtils.getProcessProvider(fileName);
        // 更改流程定义文件的xml文件name标签内容
//        businessService.changeXmlRootName(fileName);
        // 获取xml文件的输入流
        InputStream inputStream = processProvider.loadProcess(fileName);
        // 部署流程
        processService.deployProcess(inputStream);
    }

    /**
     * 更改xml文件name标签值
     *
     * @param fileName
     */
    @Override
    public void changeXmlRootName(String fileName) throws TransformerException, IOException, SAXException, ParserConfigurationException {

        String prefix = "file:";
        if (fileName.startsWith(prefix)) {
            fileName = fileName.substring(prefix.length());
        }
        // xml文件的绝对路径
        String fullPath = xmlSavePath + "/" + fileName;
        File file = new File(fullPath);
        // 获取DocumentBuilder文件构造类
        DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        // file内容复制到一个无关的xml文件中
        Document document = documentBuilder.parse(file);
        Element element = document.getDocumentElement();
        System.out.println(element);
        element.setAttribute("name", fileName);
        System.out.println(element.getAttribute("name"));

        // 获取一个转化对象，将硬盘上无关的已被修改的文件修改到uflo的存放路径中
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        Source source = new DOMSource(document);
        Result result = new StreamResult(file);
        transformer.transform(source, result);
    }

    /**
     * 查询已部署的流程的定义信息
     * 查询表: uflo_process
     *
     * @param processDefinitionName
     * @param pageNo
     * @param pageSize
     * @return
     */
    @Override
    public List<ProcessDefinition> getProcessDefinitions(String processDefinitionName, int pageNo, int pageSize) {
        ProcessQuery processQuery = processService.createProcessQuery();
        // 计算分页参数startIndex和pageSize
        // 总个数
        int count = processQuery.count();
        // 总页数
        int pageCount = count / pageSize;
        if (count % pageSize != 0) {
            pageCount++;
        }
        if (pageNo > pageCount) {
            pageNo = pageCount;
        }
        // 查询的第一个序号
        int startIndex = (pageNo - 1) * pageSize;

        if (!StringUtils.isEmpty(processDefinitionName)) {
            processQuery.nameLike("%" + processDefinitionName + "%");
        }
        List<ProcessDefinition> processDefinitions = processQuery.addOrderDesc("createDate")
                .addOrderDesc("version")
                .page(startIndex, pageSize)
                .list();
        return processDefinitions;
    }

    /**
     * 根据processId获取整个流程的url
     *
     * @param processId
     * @return
     */
    private String getStartProcessUrl(Long processId) {
        ProcessQuery processQuery = processService.createProcessQuery();
        List<ProcessDefinition> processDefinitions = processQuery.id(processId).list();
        return processDefinitions.get(0).getStartProcessUrl();
    }

    /**
     * 拆分url
     *
     * @param url
     * @return
     */
    private Map<String, String> splitUrl(String url) {
        Map<String, String> result = new HashMap<>();
        String[] firstSplitUrl = url.split("#");
        for (String splitUrl : firstSplitUrl) {
            String[] secondSplitUrl = splitUrl.split(":");
            result.put(secondSplitUrl[0], secondSplitUrl[1]);
        }
        return result;
    }

    /**
     * 对url进行拆分并获取表单设计json
     *
     * @param url
     * @return
     */
    private Map<String, Object> getFormDesignerByUrl(String url) {
        Map<String, Object> formDesigner = new HashMap<>();
        // 将url拆分
        Map<String, String> splitUrl = splitUrl(url);
        // 获取表单设计器id
        String formDesignerId = splitUrl.get("formDesignerId");
        if (StringUtils.isNotBlank(formDesignerId)) {
            String getContentResult = formDesignerService.getConent(formDesignerId).getResult().toString();
            JSONObject formDesignerJson = JSON.parseObject(getContentResult);
            formDesigner.put("formDesignerId", formDesignerId);
            formDesigner.put("formDesignerJson", formDesignerJson);
            return formDesigner;
        }
        return null;
    }

    /**
     * 保存表单数据
     *
     * @param formDesignerId
     * @param contentJson
     * @return
     */
    private Map<String, String> saveFormData(String formDesignerId, JSONObject contentJson) {
        Map<String, String> formInfo = new HashMap<>();
        // 获取表单设计器对应json
        String getContentResult = formDesignerService.getConent(formDesignerId).getResult().toString();
        JSONObject formDesignerJson = JSON.parseObject(getContentResult);
        // 获取表单设计器对应的表名
        String tableName = formDesignerJson.getJSONObject("config").getString("dataBase");
        // 调用online接口存入表单数据,返回数据id
        String dataId = onlineService.addCrazyForm(tableName, contentJson).getResult().toString();
        // 获取online对应的表id
        String tableId = formDesignerJson.getJSONObject("config").getString("tableId");
        formInfo.put("onlineTableId", tableId);
        formInfo.put("onlineDataId", dataId);
        return formInfo;
    }

    /**
     * 获取节点拥有的变量值
     *
     * @param node
     * @param contentJson
     * @return
     */
    private Map<String, Object> setVariables(Node node, JSONObject contentJson) {
        Map<String, Object> map = new HashMap<>(16);
        // 寻找节点描述，获取该节点可设置的变量名
        String description = node.getDescription();
        if (StringUtils.isNotBlank(description)) {
            // 从description中取变量名
            String[] variables = description.split(",");
            for (String variable : variables) {
                // 获取变量名对应的值
                Object value = contentJson.get(variable);
                if (value == null) {
                    throw new NullPointerException("无" + variable + "变量值");
                }
                // 设置变量名对应的值
                map.put(variable, value);
            }
        }
        return map;
    }

    /**
     * 获取整个流程的表单设计
     *
     * @param processId
     * @return
     */
    @Override
    public Map<String, Object> getStartFormDesigner(Long processId) {
        // 获取流程总体url
        String startProcessUrl = getStartProcessUrl(processId);
        // 查询对应表单设计id + json
        if (startProcessUrl != null) {
            return getFormDesignerByUrl(startProcessUrl);
        }
        return null;
    }


    /**
     * 填写表单后，启动流程
     *
     * @param processId
     * @Transactional(rollbackFor = Exception.class)抛出异常时回滚，当出现Exception异常时回滚
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long startProcess(Long processId, String userId, JSONObject contentJson) {

        TaskInfo taskInfo = new TaskInfo();
        String taskInfoId = UUID.randomUUID().toString().replace("-", "");
        taskInfo.setId(taskInfoId);

        // 处理流程总体表单(填写信息)及设置后续变量值
        // 获取流程表单设计json
        Map<String, Object> formDesigner = getStartFormDesigner(processId);
        Map<String, Object> variables = new HashMap<>();
        if (formDesigner != null) {
            String formDesignerId = (String) formDesigner.get("formDesignerId");
            Map<String, String> formInfo = saveFormData(formDesignerId, contentJson);
            taskInfo.setFormDesignerId(formDesignerId);
            taskInfo.setOnlineTableId(formInfo.get("onlineTableId"));
            taskInfo.setOnlineDataId(formInfo.get("onlineDataId"));

            // 设置后续节点变量值
            ProcessDefinition process = processService.getProcessById(processId);
            // 查找开始节点描述，设置该节点可设置的后续变量值
            Node node = process.getStartNode();
            variables = setVariables(node, contentJson);

        }
        // 设置开启流程信息参数
        StartProcessInfo startProcessInfo = new StartProcessInfo();
        startProcessInfo.setPromoter(userId);
        startProcessInfo.setBusinessId(taskInfoId);
        if (!variables.isEmpty()) {
            startProcessInfo.setVariables(variables);
        }
        // 开启流程实例
        ProcessInstance processInstance = processService.startProcessById(processId, startProcessInfo);
        if (processInstance == null) {
            throw new NullPointerException("开启流程实例失败");
        }
        // 设置taskInfo表信息
        Long processInstanceId = processInstance.getId();
        taskInfo.setProcessInstanceId(processInstanceId);
        // 获取开始节点
        HistoryTask historyTask = historyService.createHistoryTaskQuery()
                .processInstanceId(processInstanceId)
                .addOrderAsc("createDate")
                .list().get(0);
        taskInfo.setTaskId(historyTask.getTaskId());
        taskInfoMapper.insert(taskInfo);
        return processInstanceId;
    }

    /**
     * 获取当前节点的表单设计
     *
     * @param taskId
     * @return
     */
    @Override
    public Map<String, Object> getTaskFormDesigner(Long taskId) {
        // 获取任务对应的url
        Task task = taskService.getTask(taskId);
        String url = task.getUrl();
        // 查询对应表单设计id + json
        if (url != null) {
            return getFormDesignerByUrl(url);
        }
        return null;
    }

    /**
     * 查询taskId节点表单设计和内容(仅适用于已经完成的节点)
     *
     * @param taskId
     * @return
     */
    @Override
    public Map<String, Object> getFormDesignerAndData(Long processInstanceId, Long taskId) {
        Map<String, Object> map = new HashMap<>();
        QueryWrapper<TaskInfo> wrapper = new QueryWrapper();
        wrapper.eq("task_id", taskId);
        wrapper.eq("process_instance_id", processInstanceId);
        TaskInfo taskInfo = taskInfoMapper.selectOne(wrapper);
        String formDesignerId = taskInfo.getFormDesignerId();
        String getContentResult = formDesignerService.getConent(formDesignerId).getResult().toString();
        JSONObject formDesignerJson = JSON.parseObject(getContentResult);
        String onlineTableId = taskInfo.getOnlineTableId();
        String onlineDataId = taskInfo.getOnlineDataId();
        Object data = onlineService.getForm(onlineTableId, onlineDataId).getResult();
        map.put("taskId", taskId);
        map.put("formDesigner", formDesignerJson);
        map.put("formData", data);
        return map;
    }

    /**
     * 获取当前表单设计及前几个节点表单及数据
     *
     * @param taskId
     * @return
     */
    @Override
    public Map<String, Object> getForm(Long taskId) {
        Map<String, Object> taskForm = new HashMap<>(16);
        // 获取流程实例id
        Task task = taskService.getTask(taskId);
        long processInstanceId = task.getProcessInstanceId();
        // 查询同一流程实例，该task前的所有节点
        QueryWrapper<TaskInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("process_instance_id", processInstanceId);
        queryWrapper.orderByAsc("task_id");
        queryWrapper.lt("task_id", taskId);
        List<TaskInfo> taskInfoList = taskInfoMapper.selectList(queryWrapper);
        // 查询对应的所有表单设计和内容
        List<Map<String, Object>> preNodeFormDesignerAndDatas = new ArrayList<>();
        for (TaskInfo taskInfo : taskInfoList) {
            Map<String, Object> nodeForm = getFormDesignerAndData(processInstanceId, taskInfo.getTaskId());
            preNodeFormDesignerAndDatas.add(nodeForm);
        }
        taskForm.put("processInstanceId", processInstanceId);
        taskForm.put("preNodeFormDesignerAndDataList", preNodeFormDesignerAndDatas);
        taskForm.put("nowNodeFormDesigner", getTaskFormDesigner(taskId));
        return taskForm;
    }

    @Override
    public List<Task> getTodoTasks(String userId) {
        List<Task> taskList = taskService.createTaskQuery()
                .addAssignee(userId)
                .addTaskState(TaskState.Created)
                .list();
        for(Task task : taskList){
            task.setTaskParticipators(null);
        }
        return taskList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void completeTask(Long taskId, JSONObject contentJson) {

        Task task = taskService.getTask(taskId);

        TaskInfo taskInfo = new TaskInfo();
        String taskInfoId = UUID.randomUUID().toString().replace("-", "");
        taskInfo.setId(taskInfoId);

        // 获取流程表单设计json
        Map<String, Object> formDesigner = getTaskFormDesigner(taskId);
        Map<String, Object> variables = new HashMap<>(16);
        if (formDesigner != null) {
            // 填写表单
            String formDesignerId = (String) formDesigner.get("formDesignerId");
            Map<String, String> formInfo = saveFormData(formDesignerId, contentJson);
            taskInfo.setFormDesignerId(formDesignerId);
            taskInfo.setOnlineTableId(formInfo.get("onlineTableId"));
            taskInfo.setOnlineDataId(formInfo.get("onlineDataId"));
            // 设置后续节点变量值
            ProcessDefinition process = processService.getProcessById(task.getProcessId());
            // 查找开始节点描述，设置该节点可设置的后续变量值
            Node node = process.getNode(task.getTaskName());
            variables = setVariables(node, contentJson);
        }

        taskInfo.setTaskId(taskId);
        taskInfo.setProcessInstanceId(task.getProcessInstanceId());
        taskInfoMapper.insert(taskInfo);
        taskService.start(taskId);
        if (!variables.isEmpty()) {
            taskService.complete(taskId, variables);
        } else {
            taskService.complete(taskId);
        }
    }

    @Override
    public String decode(String str) {
        if (str == null) {
            return str;
        }
        try {
            str = URLDecoder.decode(str, "utf-8");
            str = URLDecoder.decode(str, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return str;
    }

}
